/** @file
  Esrt driver that publishes ESRT table to OS. Aslo install EfiEsrtOperationProtocol for other component
  to update ESRT table.

 @copyright
  INTEL CONFIDENTIAL
  Copyright 2012 - 2016 Intel Corporation.

  The source code contained or described herein and all documents related to the
  source code ("Material") are owned by Intel Corporation or its suppliers or
  licensors. Title to the Material remains with Intel Corporation or its suppliers
  and licensors. The Material may contain trade secrets and proprietary and
  confidential information of Intel Corporation and its suppliers and licensors,
  and is protected by worldwide copyright and trade secret laws and treaty
  provisions. No part of the Material may be used, copied, reproduced, modified,
  published, uploaded, posted, transmitted, distributed, or disclosed in any way
  without Intel's prior express written permission.

  No license under any patent, copyright, trade secret or other intellectual
  property right is granted to or conferred upon you by disclosure or delivery
  of the Materials, either expressly, by implication, inducement, estoppel or
  otherwise. Any license under such intellectual property rights must be
  express and approved by Intel in writing.

  Unless otherwise agreed by Intel in writing, you may not remove or alter
  this notice or any other notice embedded in Materials by Intel or
  Intel's suppliers or licensors in any way.

  This file contains a 'Sample Driver' and is licensed as such under the terms
  of your license agreement with Intel or your vendor. This file may be modified
  by the user, subject to the additional terms of the license agreement.

@par Specification Reference:
  - Windows UEFI Firmware Update Platform dated April 4, 2013 (http://www.microsoft.com/en-us/download/confirmation.aspx?id=38405)

**/

#include "EsrtDxe.h"
#include <Guid/GlobalVariable.h>

#include <Library/BoardConfigLib.h>

GLOBAL_REMOVE_IF_UNREFERENCED ESRT_OPERATION_PROTOCOL    mEsrtOperationProtocol = {
  EsrtPopulateTable,
  EsrtUpdateTableEntryByGuid,
  EsrtGetFwEntryByGuid
};

GLOBAL_REMOVE_IF_UNREFERENCED CONST UINT16 ESRT_MAX_ENTRY=256;


#define NUM_ESRT_ENTRIES  3

//
// Function implemenations
//

/**
  This function gets the SystemFirmware guild.

  @param[in]  FirmwareId   Pointer to SystemFirmware guid.

  @retval     EFI_SUCCESS  The function completes successfully
  @retval     others
**/
EFI_STATUS
EFIAPI
EsrtGetFirmwareGuid (
  IN OUT EFI_GUID               *FirmwareId
  )
{
  EFI_STATUS                    Status;
  EFI_GUID                      EsrtFirmwareId;

  //
  // Retrieve Board Config Block
  //

  CopyGuid (&EsrtFirmwareId, (EFI_GUID *) PcdGetPtr (PcdEsrtFirmwareId));
  if (EsrtFirmwareId.Data1 == 0) {
    Status = EFI_OUT_OF_RESOURCES;
  } else {
    CopyGuid (FirmwareId, &EsrtFirmwareId);
    Status = EFI_SUCCESS;
  }

  return Status;
}

/**
  ESRT  DXE module entry point

  @param[in]  ImageHandle          Not used.
  @param[in]  SystemTable          Global system service table.

  @retval     EFI_SUCCESS          Initialization complete.
  @retval     EFI_UNSUPPORTED      The chipset is unsupported by this driver.
  @retval     EFI_OUT_OF_RESOURCES Do not have enough resources to initialize the driver.
  @retval     EFI_DEVICE_ERROR     Device error, driver exits abnormally.

**/
EFI_STATUS
EFIAPI
EsrtDriverEntryPoint (
  IN EFI_HANDLE                ImageHandle,
  IN EFI_SYSTEM_TABLE       *SystemTable
  )
{
  EFI_STATUS                      Status;
  EFI_SYSTEM_RESOURCE_ENTRY_LIST  FwEntryList;
  UINTN                           Size;
  EFI_GUID                        gSystemFirmwareGuid;

  Size = sizeof(UINT32) + (NUM_ESRT_ENTRIES * sizeof(EFI_SYSTEM_RESOURCE_ENTRY));

  //
  // Get the gSystemFirmwareGuid, which is corresponding BoardID.Make sure this guid matches with the one in BiosUpdate.inf.
  //
  Status = EsrtGetFirmwareGuid (&gSystemFirmwareGuid);
  if (EFI_ERROR(Status)) {
    return EFI_DEVICE_ERROR;
  }

  Status = gRT->GetVariable (
                  L"FwEntry",
                  &gFwEntryVariableGuid,
                  NULL,
                  &Size,
                  &FwEntryList
                  );

  if (EFI_ERROR (Status)) {
    Status = gRT->GetVariable (
                    L"FwEntry",
                    &gEfiCallerIdGuid,
                    NULL,
                    &Size,
                    &FwEntryList
                    );
    if (!EFI_ERROR(Status)) {
      Status = gRT->SetVariable(
                      L"FwEntry",
                      &gFwEntryVariableGuid,
                      EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_NON_VOLATILE,
                      Size,
                      &FwEntryList
                      );

      Status = gRT->SetVariable (
                      L"FwEntry",
                      &gEfiCallerIdGuid,
                      0,
                      0,
                      NULL
                      );
    } else {
      //
      // Failed to get FwEntry for ESRT table. Create one assuming no FW entries has been created before
      //
      FwEntryList.NumEntries = NUM_ESRT_ENTRIES;
      FwEntryList.FwEntries[0].FwClass = gSystemFirmwareGuid;
      FwEntryList.FwEntries[0].FwType = 0x01;
      FwEntryList.FwEntries[0].FwVersion = 0x01;
      FwEntryList.FwEntries[0].LowestSupportedFwVersion = 0;
      FwEntryList.FwEntries[0].CapsuleFlags = 0;
      FwEntryList.FwEntries[0].LastAttemptVersion = 0x01;
      FwEntryList.FwEntries[0].LastAttemptStatus = 0;

      FwEntryList.FwEntries[1].FwClass = gDevFwGuid;
      FwEntryList.FwEntries[1].FwType = 0x02;
      FwEntryList.FwEntries[1].FwVersion = 0x01;
      FwEntryList.FwEntries[1].LowestSupportedFwVersion = 0;
      FwEntryList.FwEntries[1].CapsuleFlags = 0;
      FwEntryList.FwEntries[1].LastAttemptVersion = 0x01;
      FwEntryList.FwEntries[1].LastAttemptStatus = 0;

      FwEntryList.FwEntries[2].FwClass = gDevFwEInkGuid;
      FwEntryList.FwEntries[2].FwType = 0x02;
      FwEntryList.FwEntries[2].FwVersion = 0x01;
      FwEntryList.FwEntries[2].LowestSupportedFwVersion = 0;
      FwEntryList.FwEntries[2].CapsuleFlags = 0;
      FwEntryList.FwEntries[2].LastAttemptVersion = 0x01;
      FwEntryList.FwEntries[2].LastAttemptStatus = 0;

      Status = gRT->SetVariable (
                      L"FwEntry",
                      &gFwEntryVariableGuid,
                      EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_NON_VOLATILE,
                      Size,
                      &FwEntryList
                      );
    }
    if (EFI_ERROR(Status)) {
      return EFI_DEVICE_ERROR;
    }
  }

  Status = EsrtPopulateTable ();

  if (EFI_ERROR(Status)) {
    return Status;
  }

  Status = gBS->InstallMultipleProtocolInterfaces (
                  &ImageHandle,
                  &gEsrtOperationProtocolGuid,
                  &mEsrtOperationProtocol,
                  NULL
                  );

  return Status;
}

/**
  Create ESRT Configuration Table

  @retval     EFI_SUCCESS  The function completes successfully
  @retval     EFI_UNSUPPORTED      Can't find the entry.
  @retval     EFI_DEVICE_ERROR     Device error, function exits abnormally.
**/
EFI_STATUS
EFIAPI
EsrtPopulateTable(
  VOID
  )
{
  EFI_STATUS                     Status;
  EFI_CONFIGURATION_TABLE        *EfiConfigurationTable;
  EFI_SYSTEM_RESOURCE_TABLE      *EfiEsrtTable = NULL;
  EFI_PHYSICAL_ADDRESS           PhysicalAddress;
  EFI_SYSTEM_RESOURCE_ENTRY      *FwEntryTable = NULL;
  EFI_SYSTEM_RESOURCE_ENTRY_LIST FwEntryList;
  UINTN                          Size;
  UINT32                         FwEntryCounts = 0;
  UINT32                         FwEntryIdx;
  UINTN                          Pages;
  //
  // First publish the Esrt table from local variable.
  // @todo: don't assume max 256 entries. Call GetVariable first to get the size of the table.
  //
  Size = (sizeof(EFI_SYSTEM_RESOURCE_ENTRY) * ESRT_MAX_ENTRY) + sizeof(UINT32);

  Status = gRT->GetVariable (
                  L"FwEntry",
                  &gFwEntryVariableGuid,
                  NULL,
                  &Size,
                  &FwEntryList
                  );
  if(EFI_ERROR(Status)){
      return EFI_UNSUPPORTED;
  }

  FwEntryCounts = FwEntryList.NumEntries;
  //
  // Get from the variable maximum entries required to set up ESRT table
  //
  if ((FwEntryCounts == 0) || (FwEntryCounts > ESRT_MAX_ENTRY)) {
    //
    // Entries must be between 0 & 256;
    //
    return EFI_UNSUPPORTED;
  }

  Status = EfiGetSystemConfigurationTable (
             &gEfiSystemResourceTableGuid,
             (VOID**)&EfiConfigurationTable
             );

  if (!EFI_ERROR(Status)) {
    //
    // ESRT table exists. Need to remvoe it first.
    //
    Status = gBS->InstallConfigurationTable (
                    &gEfiSystemResourceTableGuid,
                    (VOID**)NULL
                    );

    if(EFI_ERROR(Status)){
        return EFI_DEVICE_ERROR;
    }
  }

  Pages = EFI_SIZE_TO_PAGES ((sizeof(EFI_SYSTEM_RESOURCE_ENTRY) * FwEntryCounts) + sizeof(EFI_SYSTEM_RESOURCE_TABLE));
  PhysicalAddress = (EFI_PHYSICAL_ADDRESS) (UINTN) AllocateReservedPages (Pages);

  if (PhysicalAddress == 0) {
    return EFI_OUT_OF_RESOURCES;
  }

  //
  // Insert Table header first.
  //
  EfiEsrtTable = (EFI_SYSTEM_RESOURCE_TABLE *) (UINTN) PhysicalAddress;
  FwEntryTable = (EFI_SYSTEM_RESOURCE_ENTRY *) ((UINTN)PhysicalAddress + sizeof(EFI_SYSTEM_RESOURCE_TABLE));

  EfiEsrtTable->FwResourceCount = FwEntryCounts;
  EfiEsrtTable->FwResourceCountMax = ESRT_MAX_ENTRY;
  EfiEsrtTable->FwResourceVersion = 0x01;

  //
  // Continue to update FW Resource entries.
  //
  for (FwEntryIdx = 0; FwEntryIdx < FwEntryCounts; FwEntryIdx++) {
    FwEntryTable[FwEntryIdx].FwType                   = FwEntryList.FwEntries[FwEntryIdx].FwType;
    FwEntryTable[FwEntryIdx].FwVersion                = FwEntryList.FwEntries[FwEntryIdx].FwVersion;
    FwEntryTable[FwEntryIdx].LowestSupportedFwVersion = FwEntryList.FwEntries[FwEntryIdx].LowestSupportedFwVersion;
    FwEntryTable[FwEntryIdx].CapsuleFlags             = FwEntryList.FwEntries[FwEntryIdx].CapsuleFlags;
    FwEntryTable[FwEntryIdx].LastAttemptVersion       = FwEntryList.FwEntries[FwEntryIdx].LastAttemptVersion;
    FwEntryTable[FwEntryIdx].LastAttemptStatus        = FwEntryList.FwEntries[FwEntryIdx].LastAttemptStatus;
    FwEntryTable[FwEntryIdx].FwClass                  = FwEntryList.FwEntries[FwEntryIdx].FwClass;
  }

  //
  //Install the table
  //
  Status = gBS->InstallConfigurationTable (
                  &gEfiSystemResourceTableGuid,
                  (VOID *) EfiEsrtTable
                  );

  if (EFI_ERROR(Status)) {
    //
    //Fail to install the table.
    //
    return EFI_DEVICE_ERROR;
  }

  return EFI_SUCCESS;
}

/**
  Update Esrt table entry

  @param[in]  FwEntryGuid          Specify which entry to update.
  @param[in]  FwEntry              Pointer to the entry content.

  @retval     EFI_SUCCESS  The function completes successfully
  @retval     EFI_UNSUPPORTED      Can't find the entry.
  @retval     EFI_DEVICE_ERROR     Device error, function exits abnormally.
**/
EFI_STATUS
EFIAPI
EsrtUpdateTableEntryByGuid(
  IN EFI_GUID FwEntryGuid,
  IN EFI_SYSTEM_RESOURCE_ENTRY *FwEntry
  )
{
  //
  // This is a two step operation: first update the variable, then publish the table again.
  //
  EFI_STATUS                      Status;
  EFI_SYSTEM_RESOURCE_ENTRY_LIST  NewFwEntryList;
  EFI_SYSTEM_RESOURCE_ENTRY_LIST  FwEntryList;
  UINTN                           Size;
  UINT32                          FwEntryCounts = 0;
  UINT32                          NewFwEntryCounts = 0;
  UINT32                          FwEntryIdx;
  UINT32                          NewFwEntryIdx;
  BOOLEAN                         MatchFound = FALSE;

  //
  // First publish the Esrt table from local variable.
  //
  Size = (sizeof(EFI_SYSTEM_RESOURCE_ENTRY) * ESRT_MAX_ENTRY) + 4;

  Status = gRT->GetVariable (
                  L"FwEntry",
                  &gFwEntryVariableGuid,
                  NULL,
                  &Size,
                  &FwEntryList
                  );

  if (EFI_ERROR(Status)) {
    //
    // Failed to find ESRT entries. ESRT tables cannot be published.
    //
    return EFI_UNSUPPORTED;
  }

  FwEntryCounts = FwEntryList.NumEntries;
  //
  // Get from the variable maximum entries required to set up ESRT table
  //
  if(FwEntryCounts == 0 || FwEntryCounts >ESRT_MAX_ENTRY){
    //
    // Entries must be between 0 & 256;
    //
    return EFI_UNSUPPORTED;
  }

  //
  // Go thru FwEntryList, decides to add/remove/modify the table entry according to guid.
  //
  for (FwEntryIdx = 0, NewFwEntryIdx = 0; FwEntryIdx < FwEntryCounts; FwEntryIdx++) {
    if (!CompareGuid(&(FwEntryList.FwEntries[FwEntryIdx].FwClass), &FwEntryGuid)) {
      //
      // For entris that does not match this GUID, simple copy it over to new variable
      //
      NewFwEntryList.FwEntries[NewFwEntryIdx].FwType = FwEntryList.FwEntries[FwEntryIdx].FwType;
      NewFwEntryList.FwEntries[NewFwEntryIdx].FwVersion = FwEntryList.FwEntries[FwEntryIdx].FwVersion;
      NewFwEntryList.FwEntries[NewFwEntryIdx].LowestSupportedFwVersion = FwEntryList.FwEntries[FwEntryIdx].LowestSupportedFwVersion;
      NewFwEntryList.FwEntries[NewFwEntryIdx].CapsuleFlags = FwEntryList.FwEntries[FwEntryIdx].CapsuleFlags;
      NewFwEntryList.FwEntries[NewFwEntryIdx].LastAttemptVersion = FwEntryList.FwEntries[FwEntryIdx].LastAttemptVersion;
      NewFwEntryList.FwEntries[NewFwEntryIdx].LastAttemptStatus = FwEntryList.FwEntries[FwEntryIdx].LastAttemptStatus;
      NewFwEntryList.FwEntries[NewFwEntryIdx].FwClass = FwEntryList.FwEntries[FwEntryIdx].FwClass;
      NewFwEntryCounts++;
      NewFwEntryIdx++;
      //
      // In case FwEntryCount is reached and we still don't have a match... we need to add an entry.
      // However, this seems not going to happen, since win8 will not allow this entry to be updated.
      //
      if (((FwEntryIdx + 1) == FwEntryCounts) && (MatchFound == FALSE)) {
        //
        // Add request
        //
        if ((FwEntryIdx + 1) == ESRT_MAX_ENTRY) {
          //
          // Max entries reached. No space for allocation.
          //
          return EFI_OUT_OF_RESOURCES;
        } else { // Remaining slots for new entry. Add it.
          if(NewFwEntryIdx >= ESRT_MAX_ENTRY){
            return EFI_OUT_OF_RESOURCES;
          }
          NewFwEntryList.FwEntries[NewFwEntryIdx].FwType                   = FwEntry->FwType;
          NewFwEntryList.FwEntries[NewFwEntryIdx].FwVersion                = FwEntry->FwVersion;
          NewFwEntryList.FwEntries[NewFwEntryIdx].LowestSupportedFwVersion = FwEntry->LowestSupportedFwVersion;
          NewFwEntryList.FwEntries[NewFwEntryIdx].CapsuleFlags             = FwEntry->CapsuleFlags;
          NewFwEntryList.FwEntries[NewFwEntryIdx].LastAttemptVersion       = FwEntry->LastAttemptVersion;
          NewFwEntryList.FwEntries[NewFwEntryIdx].LastAttemptStatus        = FwEntry->LastAttemptStatus;
          NewFwEntryList.FwEntries[NewFwEntryIdx].FwClass                  = FwEntry->FwClass;
          NewFwEntryCounts++;
        }
      }
    } else { // A matching record is found.
      if (FwEntry == NULL) {
        //
        // If it's a remove request, then we don't update this into new fw entry list.
        //
        MatchFound = TRUE;
        continue;
      } else { // A modify request.
        NewFwEntryList.FwEntries[NewFwEntryIdx].FwType                   = FwEntry->FwType;
        NewFwEntryList.FwEntries[NewFwEntryIdx].FwVersion                = FwEntry->FwVersion;
        NewFwEntryList.FwEntries[NewFwEntryIdx].LowestSupportedFwVersion = FwEntry->LowestSupportedFwVersion;
        NewFwEntryList.FwEntries[NewFwEntryIdx].CapsuleFlags             = FwEntry->CapsuleFlags;
        NewFwEntryList.FwEntries[NewFwEntryIdx].LastAttemptVersion       = FwEntry->LastAttemptVersion;
        NewFwEntryList.FwEntries[NewFwEntryIdx].LastAttemptStatus        = FwEntry->LastAttemptStatus;
        NewFwEntryList.FwEntries[NewFwEntryIdx].FwClass                  = FwEntry->FwClass;
        NewFwEntryCounts++;
        NewFwEntryIdx++;
        MatchFound = TRUE;
        continue;
      }
    }
  }

  if (NewFwEntryCounts > 0) {
    //
    // Publish the table now.
    //
    NewFwEntryList.NumEntries = NewFwEntryCounts;
    Size = sizeof(EFI_SYSTEM_RESOURCE_ENTRY)*NewFwEntryCounts + 4;

    Status = gRT->SetVariable (
                    L"FwEntry",
                    &gFwEntryVariableGuid,
                    EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_NON_VOLATILE,
                    Size,
                    &NewFwEntryList
                    );
    if (EFI_ERROR(Status)) {
      return EFI_DEVICE_ERROR;
    } else {
      Status = EsrtPopulateTable();
      return Status;
    }
  } else {
    return EFI_DEVICE_ERROR;
  }
}

/**
  Get Esrt table entry

  @param[in]  FwEntryGuid          Specify which entry to update.
  @param[in]  FwEntry              Pointer to the entry content.

  @retval     EFI_SUCCESS  The function completes successfully
  @retval     EFI_NOT_FOUND        Can't find the entry.
  @retval     EFI_DEVICE_ERROR     Device error, function exits abnormally.
**/
EFI_STATUS
EFIAPI
EsrtGetFwEntryByGuid (
  IN EFI_GUID FwEntryGuid,
  OUT EFI_SYSTEM_RESOURCE_ENTRY *FwEntry
  )
{
  EFI_STATUS                 Status;
  EFI_CONFIGURATION_TABLE    *EfiConfigurationTable;
  EFI_SYSTEM_RESOURCE_TABLE  *EfiEsrtTable = NULL;
  EFI_SYSTEM_RESOURCE_ENTRY  *FwEntryTable = NULL;
  UINT32                     TotalFwResCount = 0;
  UINT32                     FwEntryIdx = 0;
  BOOLEAN                    MatchFound = FALSE;

  if (FwEntry == NULL) {
    return EFI_DEVICE_ERROR;
  }
  //
  // The table is retrieved from system configuration table. Never retrieve this from variable.
  //
  Status = EfiGetSystemConfigurationTable (
             &gEfiSystemResourceTableGuid,
             (VOID**) &EfiConfigurationTable
             );

  if (EFI_ERROR(Status) || (EfiConfigurationTable == NULL)) {
    return Status;
  }

  EfiEsrtTable = (EFI_SYSTEM_RESOURCE_TABLE *) (UINTN)EfiConfigurationTable;
  FwEntryTable = (EFI_SYSTEM_RESOURCE_ENTRY *) ((UINTN)EfiConfigurationTable + sizeof(EFI_SYSTEM_RESOURCE_TABLE));

  TotalFwResCount = EfiEsrtTable->FwResourceCount;

  if (TotalFwResCount < 1) {
    return EFI_DEVICE_ERROR;
  }

  for (FwEntryIdx = 0; FwEntryIdx < TotalFwResCount; FwEntryIdx++) {
    if (CompareGuid(&FwEntryGuid, &(FwEntryTable[FwEntryIdx].FwClass))) {
      //
      // Match
      //
      FwEntry->FwClass                  = FwEntryTable[FwEntryIdx].FwClass;
      FwEntry->FwType                   = FwEntryTable[FwEntryIdx].FwType;
      FwEntry->FwVersion                = FwEntryTable[FwEntryIdx].FwVersion;
      FwEntry->LowestSupportedFwVersion = FwEntryTable[FwEntryIdx].LowestSupportedFwVersion;
      FwEntry->CapsuleFlags             = FwEntryTable[FwEntryIdx].CapsuleFlags;
      FwEntry->LastAttemptVersion       = FwEntryTable[FwEntryIdx].LastAttemptVersion;
      FwEntry->LastAttemptStatus        = FwEntryTable[FwEntryIdx].LastAttemptStatus;
      FwEntry->FwClass                  = FwEntryTable[FwEntryIdx].FwClass;

      MatchFound = TRUE;
      break;
    }
  }

  if (MatchFound) {
    return EFI_SUCCESS;
  } else {
    return EFI_NOT_FOUND;
  }
}
